昨天我們聊到, node 的 JS 層中的 IO method , 在作了相應的設定後, 就利用 XXX.on 這類 node api 把事件與其回調函數註冊進 libuv 了, 其中 libuv 就是 node 處理非同步 IO 的地方, 之後細聊, 今天我們先來了解, JS 把事件及其回調函數註冊進 libuv 這件事的本質是甚麼。
觀察這張圖, 其中 APPLICATION 就是我們平常撰寫的 JS 程式碼, 而 V8 則是大名鼎鼎的 JS 驅動引擎,
https://zh.wikipedia.org/wiki/V8_(JavaScript引擎)
其實如果到此為止那就是我們一般看到的網頁結構。
而今天 node 在 V8 下面加上了 C++ 連接層, 此外 V8 在運行的時候可以在跑到特定 JS 程式碼時聯繫到 C++ 連階層的程式碼。而會被 V8 驅動引擎聯繫到 C++ 連階層的 JS 程式碼, 我個人稱之為 Node api, 而 C++ 連接層會再把透過 node api 傳入的事件及其回調函數 , 註冊進更底層的 Libuv
所以我們等下來看看 node API 是怎麼聯繫到 C++ 連接層。
從昨天的進度開始往下走, 我們來看看我們註冊事件的對象 socket 的本體
https://github.com/nodejs/node/blob/master/lib/net.js
在裡面可以看到再標頭處的引用
**const { guessHandleType } = internalBinding('util');
const { ShutdownWrap } = internalBinding('stream_wrap');
const {
TCP,
TCPConnectWrap,
constants: TCPConstants
} = internalBinding('tcp_wrap');
const {
Pipe,
PipeConnectWrap,
constants: PipeConstants
} = internalBinding('pipe_wrap');**
其中 TCP 創建了 handle
function createHandle(fd, is_server) {
validateInt32(fd, 'fd', 0);
const type = guessHandleType(fd);
if (type === 'PIPE') {
return new Pipe(
is_server ? PipeConstants.SERVER : PipeConstants.SOCKET
);
}
if (type === 'TCP') {
return new TCP(
is_server ? TCPConstants.SERVER : TCPConstants.SOCKET
);
}
throw new ERR_INVALID_FD_TYPE(type);
}
handle 被放在 socket 裡, 用來處理 node api
if (options.handle) {
this._handle = options.handle; // private
this[async_id_symbol] = getNewAsyncId(this._handle);
} else if (options.fd !== undefined) {
const { fd } = options;
let err;
// createHandle will throw ERR_INVALID_FD_TYPE if `fd` is not
// a valid `PIPE` or `TCP` descriptor
this._handle = createHandle(fd, false);
err = this._handle.open(fd);
// 後略
至此我們知道了下一步就是察看 TCP 物件, 而此時發現引用 TCP 物件用的是internalBinding('tcp_wrap');
這再 V8 的意思就是引入 C++ 檔案
終於, 我們找到了被 V8 驅動引擎聯繫到 C++ 連接層的 JS 程式碼
查看 TCP 的來源, C++連接層中的 tcp_wrap 把事件及回調函數註冊到哪了 ?
明天見 !